import java.util.*;
/*
             Set      HashSet     LinkedHashSet
                      TreeSet(堆)
 */

public class set {
    public static void main(String[] args) {

        //set 存取无序，不重复，无索引
        //set集合的实现类：HashSet，LinkedHashSet，TreeSet
        //set及其下的实现类的方法基本与collection一致，只是没有get()方法，因为set没有索引
        /*set方法
         * 1.add(E e) 添加元素
         * 2.remove(E e) 删除元素
         * 3.clear() 清空元素
         * 4.contains() 判断是否包含某个元素
         * 5.isEmpty() 判断是否为空
         * 6.size() 返回集合的长度
         * 7.iterator() 返回迭代器
         * */
        //set集合及以下的实现类的遍历方式：
        //1.迭代器遍历
        //2.增强for循环遍历
        //3.lambda表达式遍历


        //HashSet
        //HashSet是Set接口的实现类，底层是哈希表，元素是无序的，不重复的，无索引的
        //HashSet的底层是HashMap，HashMap的底层是数组+链表+红黑树
        /*
        哈希值
        根据hashCode()方法计算出来的int类型的整数
        该方法定义在Object类中，所有对象都可以调用，默认使用地址值进行计算
        一般情况下，会重写hashCode()方法，根据对象的属性值进行计算

        对象哈希值特点
        只要重写了hashCode()方法，那么哈希值就会根据对象的属性值进行计算，如果属性值相同，哈希值也相同
        小部分情况下，属性值相同，哈希值不一定相同(哈希冲突)
         */
       System.out.println("HashSet实现：");
        Student s1 = new Student("aaa", 15);
        Student s2 = new Student("aaa", 15);
        Student s3 = new Student("bbb", 15);
        Student s4 = new Student("ccc", 9);

        HashSet<Student> hashset = new HashSet<>();
        hashset.add(s1);
        hashset.add(s2); //返回false，因为s1和s2的哈希值相同，所以s2不会被添加
        hashset.add(s3);
        hashset.add(s4);
        //因为重写了toString()方法，所以输出的是对象的属性值,不然输出的是地址值
        System.out.println(hashset);//[Student{name='aaa', age=15}, Student{name='bbb', age=18}, Student{name='ccc', age=9}]


        //重写hashCode()方法后，哈希值就会根据对象的属性值进行计算，如果属性值相同，哈希值也相同
        //所以s1和s2的哈希值是相同的
        System.out.println(s1.hashCode());//2986927
        System.out.println(s2.hashCode());//2986927
        /*hashSet的底层原理
        * 1.创建一个默认长度为16，默认加载因子为0.75的数组，数组名table
        * 2.根据元素的哈希值跟数组的长度计算出应存入的位置
        * 3.判断该位置是是否为null(是否有元素)，如果没有元素，直接存入
        * 4.如果有元素，调用equals()方法比较属性值，如果属性值相同，不存入
        * 5.如果属性值不同，存入，拉链法，新元素直接存入链表的末尾
        * 6.数组长度到达16*0.75=12时，数组扩容为原来的2倍
        * 7.扩容后，需要重新计算每个元素的位置，存入新的数组
        * 8.当链表长度大于8而且数组长度到达64时，链表转换为红黑树(JDK8以后)
        *
        * 所以根据底层原理，如果我们要存入的元素是自定义类型，必须重写hashCode()方法和equals()方法
        *
        * Hashset三个问题
        * 存取无序 因为挂入链表位置随机而遍历底层数组时是从0开始遍历的，有链表遍历链表，然后继续遍历数组
        * 没有索引 数组+链表怎么有索引？
        * 怎么去重 利用hashCode()方法和equals()方法
        * */


        //LinkedHashSet
        //LinkedHashSet是HashSet的子类，底层是哈希表+链表，元素是有序的，不重复的，无索引
        //有序性是指元素的存入顺序和取出顺序相同
        //底层依旧跟HashSet一样，只是加入的元素用双向链表连接起来，所以存取有序


        //TreeSet
        //TreeSet是Set接口的实现类，底层是红黑树，元素是不重复，无索引,可排序
        //TreeSet的底层是TreeMap，TreeMap的底层是红黑树

        //对于整型和浮点型，默认从小到大
        //对于String类型，按照字母在ASCLL表顺序排序
        System.out.println("\n--------------------------------\nTreeSet实现：");
        TreeSet<Integer> treeSet = new TreeSet<>();
        treeSet.add(6);
        treeSet.add(3);
        treeSet.add(8);

        System.out.println(treeSet);//[3, 6, 8]  或者用三种遍历set的方式

        //对于自定义类型,有两种排序方式
        //1.自然排序:Javabean类实现Comparable接口，重写compareTo()方法(见Student类)
        //2.比较器排序:创建TreeSet对象时，传入Comparator比较器对象(两种方式同时存在时，会按这个定义的排序方式排序)

        TreeSet<Student> treeSet1 = new TreeSet<>();
        treeSet1.add(s1);
        treeSet1.add(s2);
        treeSet1.add(s3);
        treeSet1.add(s4);
        System.out.println(treeSet1);//[Student{name='aaa', age=15}, Student{name='bbb', age=18}, Student{name='ccc', age=9}]

       TreeSet<Student> treeSet2 = new TreeSet<>(new Comparator<Student>() {
           @Override
           public int compare(Student o1, Student o2) {
               //负数 从小到大
               //正数 从大到小
               //0 相等,不存入
               return o1.getAge() - o2.getAge();
           }
       });


      treeSet2.add(s1);
      treeSet2.add(s2);
      treeSet2.add(s3);//这个没有存入，s3年龄和s1年龄相同，所以不存入
      //也同时说明当来两种排序方式并存时，会先按比较器排序，再按自然排序
      treeSet2.add(s4);
       System.out.println(treeSet2);//[Student{name='ccc', age=9}, Student{name='aaa', age=15}]

    }
}


 class Student  implements Comparable<Student>{
    private String name;
    private int age;
//------------------------
    // 重写的compareTo()方法
    @Override
    public int compareTo(Student o) {
        /*
        this:表示当前添加的元素
        o:表示已经在红黑树中存在的元素

        返回值的三种情况(底层是红黑树，所以这个用语是针对红黑树的)
        负数：认为要添加的元素小于已经存在的元素，存左边(从小到大排序)
        正数：认为要添加的元素大于已经存在的元素，存右边
        0：认为要添加的元素等于已经存在的元素，不存

        return this.age - o.age;
         */
        int i = this.age - o.age;
        i= i == 0 ? this.name.compareTo(o.name) : i;
        return i;
    }

// --------------------------
     // 重写的hashCode()方法
     @Override
     public int hashCode() {
         return Objects.hash(name, age);
     }

     @Override
     public boolean equals(Object o) {
         if (o == null || getClass() != o.getClass()) return false;
         Student student = (Student) o;
         return age == student.age && Objects.equals(name, student.name);
     }

     //------------------------------
     // 重写的toString()方法


     @Override
     public String toString() {
         return "Student{" +
                 "name='" + name + '\'' +
                 ", age=" + age +
                 '}';
     }

     //
     public Student(String name, int age) {
         this.name = name;
         this.age = age;
     }

     public String getName() {
         return name;
     }

     public void setName(String name) {
         this.name = name;
     }

     public int getAge() {
         return age;
     }

     public void setAge(int age) {
         this.age = age;
     }

 }
